/*
 Copyright (C) 2010 Kristian Duske

 This file is part of TrenchBroom.

 TrenchBroom is free software: you can redistribute it and/or modify
 it under the terms of the GNU General Public License as published by
 the Free Software Foundation, either version 3 of the License, or
 (at your option) any later version.

 TrenchBroom is distributed in the hope that it will be useful,
 but WITHOUT ANY WARRANTY; without even the implied warranty of
 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 GNU General Public License for more details.

 You should have received a copy of the GNU General Public License
 along with TrenchBroom. If not, see <http://www.gnu.org/licenses/>.
 */

#pragma once

#include <QAbstractTableModel>

#include "kd/reflection_decl.h"

#include <iosfwd>
#include <map>
#include <string>
#include <vector>

class QObject;

namespace tb
{
namespace mdl
{
class EntityNodeBase;
class Map;
} // namespace mdl

namespace ui
{

enum class ValueState
{
  /**
   * No entities have this key set; the provided value is the default from the entity
   * definition
   */
  Unset,
  /**
   * All entities have the same value set for this key
   */
  SingleValue,
  /**
   * 1+ entities have this key unset, the rest have the same value set
   */
  SingleValueAndUnset,
  /**
   * Two or more entities have different values for this key
   */
  MultipleValues
};

std::ostream& operator<<(std::ostream& lhs, const ValueState& rhs);

enum class LinkType
{
  Source,
  Target,
  None,
};

std::ostream& operator<<(std::ostream& lhs, const LinkType& rhs);

enum class PropertyProtection
{
  NotProtectable,
  Protected,
  NotProtected,
  Mixed
};

std::ostream& operator<<(std::ostream& lhs, const PropertyProtection& rhs);

/**
 * Suggests a new, unused property name of the form "property X".
 */
std::string newPropertyKeyForEntityNodes(const std::vector<mdl::EntityNodeBase*>& nodes);

/**
 * Viewmodel (as in MVVM) for a single row in the table
 */
struct PropertyRow
{
  std::string key;
  std::string value;
  ValueState valueState = ValueState::Unset;

  bool keyMutable = true;
  bool valueMutable = true;
  PropertyProtection protection = PropertyProtection::NotProtectable;
  LinkType linkType = LinkType::None;
  std::string tooltip;

  kdl_reflect_decl(
    PropertyRow,
    key,
    value,
    valueState,
    keyMutable,
    valueMutable,
    protection,
    linkType,
    tooltip);
};

/**
 * Model for the QTableView.
 *
 * Data flow:
 *
 * 1. MapDocument is modified, or entities are added/removed from the list that
 * EntityPropertyGridTable is observing
 * 2. EntityPropertyGridTable observes the change, and builds a list of PropertyRow for
 * the new state
 * 3. The new state and old state are diffed, and the necessary QAbstractTableModel
 * methods called to update the view correctly (preserving selection, etc.)
 *
 * All edits to the table flow this way; the EntityPropertyGridTable is never modified in
 * response to a UI action.
 *
 * The order of m_rows is not significant; it's expected that there is a sort proxy model
 * used on top of this model.
 */
class EntityPropertyModel : public QAbstractTableModel
{
  Q_OBJECT
public:
  static const int ColumnProtected = 0;
  static const int ColumnKey = 1;
  static const int ColumnValue = 2;
  static const int NumColumns = 3;

private:
  std::vector<PropertyRow> m_rows;
  bool m_showDefaultRows;
  bool m_shouldShowProtectedProperties;
  mdl::Map& m_map;

public:
  explicit EntityPropertyModel(mdl::Map& map, QObject* parent = nullptr);

  bool showDefaultRows() const;
  void setShowDefaultRows(bool showDefaultRows);

  bool shouldShowProtectedProperties() const;

  const std::vector<PropertyRow>& rows() const;

  const PropertyRow* rowForModelIndex(const QModelIndex& index) const;
  int rowIndexForPropertyKey(const std::string& propertyKey) const;

  QStringList getCompletions(const QModelIndex& index) const;

public slots:
  void updateFromMap();

public: // QAbstractTableModel overrides
  int rowCount(const QModelIndex& parent) const override;
  int columnCount(const QModelIndex& parent) const override;
  Qt::ItemFlags flags(const QModelIndex& index) const override;
  QVariant data(const QModelIndex& index, int role) const override;
  bool setData(const QModelIndex& index, const QVariant& value, int role) override;
  QVariant headerData(int section, Qt::Orientation orientation, int role) const override;

private: // helpers
  std::vector<std::string> propertyKeys(int row, int count) const;

  void setRows(const std::map<std::string, PropertyRow>& newRows);
  bool hasRowWithPropertyKey(const std::string& propertyKey) const;
  bool renameProperty(
    size_t rowIndex,
    const std::string& newKey,
    const std::vector<mdl::EntityNodeBase*>& nodes);
  bool updateProperty(
    size_t rowIndex,
    const std::string& newValue,
    const std::vector<mdl::EntityNodeBase*>& nodes);
  bool setProtectedProperty(size_t rowIndex, bool newValue);

public: // EntityPropertyGrid helpers
  std::string propertyKey(int row) const;
  bool canRemove(int rowIndexInt);
  /**
   * Return the desired sort order for these two rows.
   * Used by EntitySortFilterProxyModel to sort the rows.
   */
  bool lessThan(size_t rowIndexA, size_t rowIndexB) const;
};

} // namespace ui
} // namespace tb
